package com.daffodil.storage.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import com.daffodil.storage.exception.BaseException;
import com.daffodil.storage.model.MediaMimetype;
import com.daffodil.storage.model.Page;
import com.daffodil.storage.model.StorageResult;
import com.daffodil.storage.properties.QiniuProperties;
import com.daffodil.storage.properties.StorageProperties;
import com.daffodil.storage.service.AStorageService;
import com.daffodil.storage.util.StorageUtils;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.storage.model.FileInfo;
import com.qiniu.storage.model.FileListing;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;

import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @author yweijian
 * @date 2022年12月12日
 * @version 2.0.0
 * @description
 */
@Slf4j
@Service
@Configuration
@ConditionalOnProperty(name = StorageProperties.NAME, havingValue = StorageProperties.QINIU)
public class StorageQiniuServiceImpl extends AStorageService {

    @Autowired
    private StorageProperties storageProperties;

    @Autowired
    private Auth auth;

    @Autowired
    private UploadManager uploadManager;

    @Autowired
    private BucketManager bucketManager;

    /** 限定列举文件的最大个数 */
    private static final Integer MAXKEYS = 1000;

    @Override
    public StorageResult upolad(StorageResult result, InputStream inputStream) {
        log.debug("七牛云OSS对象存储 > 上传对象 > fileName={}", result.getOrigin());

        QiniuProperties properties = storageProperties.getQiniu();
        result.setUrl(properties.getDomain() + StrUtil.SLASH + result.getKey());

        StringMap params = new StringMap();
        if(ObjectUtil.isNotEmpty(result.getMetadata())) {
            for (Map.Entry<String, String> entry : result.getMetadata().entrySet()) {
                params.put(entry.getKey(), entry.getValue());
            }
        }
        try {
            String token = auth.uploadToken(properties.getBucketName(), result.getKey());
            Response response = uploadManager.put(inputStream, result.getSize(), result.getKey(), token, params, result.getMime(), true);
            DefaultPutRet putRet = JSONUtil.toBean(response.bodyString(), DefaultPutRet.class);
            if(ObjectUtil.isNotNull(putRet)) {
                result.setETag(putRet.hash);
                log.debug("七牛云OSS对象存储 > 上传对象 > eTag={}", putRet.hash);
            }
        } catch (QiniuException e) {
            log.error(e.getMessage(), e);
            Response r = e.response;
            if(r != null) {
                throw new BaseException(r.statusCode, new Object[] {r.error});
            }else {
                throw new BaseException(e.getMessage());
            }
        }

        log.debug("七牛云OSS对象存储 > 上传对象 > result={}", result.toString());
        return result;
    }

    /**
     * -七牛云OSS不支持分页查询，这里实现是按业务文件目录查询[伪分页]。
     * @example { folder: dev-upload/ name: abc }
     * @remark 分页page.totalRow 总记录数只记录有效数据文件对象，不记录对象目录
     */
    @Override
    public List<StorageResult> list(String folder, String name, Page page) {
        String prefix = StorageUtils.getPrefix(folder, name);
        QiniuProperties properties = storageProperties.getQiniu();
        FileListing list = null;
        try {
            prefix = prefix.length() <= 1 ? StrUtil.EMPTY : prefix.toString();
            list = bucketManager.listFilesV2(properties.getBucketName(), prefix.toString(), null, MAXKEYS, StrUtil.SLASH);
        } catch (QiniuException e) {
            log.error(e.getMessage(), e);
            Response r = e.response;
            if(r != null) {
                throw new BaseException(r.statusCode, new Object[] {r.error});
            }else {
                throw new BaseException(e.getMessage());
            }
        }

        List<StorageResult> results = new ArrayList<StorageResult>();
        if(list == null) {
            return results;
        }

        String[] prefixes = list.commonPrefixes;
        for (String key : prefixes) {
            StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                    .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(0L).mime(StrUtil.EMPTY).key(key)
                    .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
            results.add(result);
        }

        //对象数据
        FileInfo[] items = list.items;
        if(ObjectUtil.isEmpty(items)) {
            return results;
        }

        List<FileInfo> infos = Arrays.asList(items).stream().filter(item -> !item.key.endsWith(StrUtil.SLASH)).collect(Collectors.toList());
        page.setTotalRow(infos.size());
        int fromIndex = page.getFromIndex();
        int toIndex = Math.min(page.getToIndex(), infos.size());
        if(fromIndex > toIndex) {
            return results;
        }

        for(int i = fromIndex; i < toIndex; i++) {
            FileInfo info = infos.get(i);
            String key = info.key;
            StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                    .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(info.fsize)
                    .mime(MediaMimetype.getInstance().getMimetype(FileNameUtil.getSuffix(key))).key(key)
                    .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
            results.add(result);
        }

        return results;
    }

    @Override
    public void download(String key, HttpServletResponse response) {
        QiniuProperties properties = storageProperties.getQiniu();
        String fileName = FileNameUtil.getName(key);
        if(ObjectUtil.isEmpty(fileName)) {
            return;
        }
        String source = properties.getDomain() + StrUtil.SLASH + key;
        InputStream inputStream = HttpRequest.get(source).timeout(30000).execute().bodyStream();

        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(fileName, StandardCharsets.UTF_8).build().toString());
        try {
            IOUtils.copy(inputStream, response.getOutputStream());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }
    }

    @Override
    public void remove(String key) {
        QiniuProperties properties = storageProperties.getQiniu();
        try {
            bucketManager.delete(properties.getBucketName(), key);
        } catch (QiniuException e) {
            log.error(e.getMessage(), e);
            Response r = e.response;
            if(r != null) {
                throw new BaseException(r.statusCode, new Object[] {r.error});
            }else {
                throw new BaseException(e.getMessage());
            }
        }
    }

}
